var Validate = {
	CONST: {
		INHERIT: 'inherit'
	},
	regex: {
		empty: /^\s+$/,
		integer: /^[-+]?\d+$/,
		float: /^\d+(\.\d+)?$/,
		alpha: /^[A-Za-z]+$/,
		email: /\w{1,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/,
		mobile: /^1(3[0-9]|5[012356789]|8[056789])\d{8}$/,
		phone: /^((\(\d{2,3}\))|(\d{3}\-))?(\(0\d{2,3}\)|0\d{2,3}-)?[1-9]\d{6,7}(\-\d{1,4})?$/,
		chinese: /^[\u0391-\uFFE5]+$/,
		url: /^http:\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\':+!]*([^<>\"])*$/,
		ip: /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
		zip: /^[0-9]\d{5}$/
	},
	rule: {
		required: function(s, emptyValue){
			if(typeof emptyValue != 'undefined')
				return s != emptyValue;
			return !Validate.rule.isEmpty(s);
		},
		isEmpty: function(s){
			return (s == null) || (s.length == 0);
		},
		isAlpha: function(s){
			return Validate.rule.isEmpty(s) || Validate.regex.alpha.test(s);
		},
		isChinese: function(s){
			return Validate.rule.isEmpty(s) || Validate.regex.chinese.test(s);
		},
		isInteger: function(s){
			return Validate.rule.isEmpty(s) || Validate.regex.integer.test(s);
		},
		isFloat: function(s){
			return Validate.rule.isEmpty(s) || Validate.regex.float.test(s);
		},
		isAlphaNum: function(){
			//
		},
		isMobile: function(s){
			return Validate.rule.isEmpty(s) || Validate.regex.mobile.test(s);
		},
		isPhone: function(s){
			return Validate.rule.isEmpty(s) || Validate.regex.phone.test(s);
		},
		isEmail: function(s){
			return Validate.rule.isEmpty(s) || Validate.regex.email.test(s);
		},
		isURL: function(s){
			return Validate.rule.isEmpty(s) || Validate.regex.url.test(s);
		},
		regex: function(s, regex){
			return Validate.rule.isEmpty(s) || regex.test(s);
		},
		func: function(s, validFunction){
			return validFunction(s);
		},
		ajax: function(s, url, callback){
			var valid = {valid: s}.toParamString();
			new Ajax.Request(url, {
				method: 'POST',
				parameters: valid,
				onSuccess: callback
			})
		},
		isDatetime: function(dt, format){//thanks wofoshanren
			if(Validate.rule.isEmpty(dt))return true;
			var format = format || 'yyyy-MM-dd';
			var o = {}, d = new Date();
			var f1 = format.split(/[^a-z]+/gi), f2 = dt.split(/\D+/g), f3 = format.split(/[a-z]+/gi), f4 = dt.split(/\d+/g);
			var len = f1.length, len1 = f3.length;
			if(len != f2.length || len1 != f4.length) return [false, format];
			for(var i = 0; i < len1; i ++)if(f3[i] != f4[i]) return [false, format];
			for(var i = 0; i < len; i ++) o[f1[i]] = f2[i];
			o.yyyy = s(o.yyyy, o.yy, d.getFullYear(), 9999, 4);
			o.MM = s(o.MM, o.M, d.getMonth() + 1, 12);
			o.dd = s(o.dd, o.d, d.getDate(), 31);
			o.hh = s(o.hh, o.h, d.getHours(), 24);
			o.mm = s(o.mm, o.m, d.getMinutes());
			o.ss = s(o.ss, o.s, d.getSeconds());
			o.ms = s(o.ms, o.ms, d.getMilliseconds(), 999, 3);
			if(o.yyyy + o.MM + o.dd + o.hh + o.mm + o.ss + o.ms < 0) return [false, format];
			if(o.yyyy < 100) o.yyyy += (o.yyyy > 30 ? 1900 : 2000);
			d = new Date(o.yyyy, o.MM - 1, o.dd, o.hh, o.mm, o.ss, o.ms);
			var reVal = d.getFullYear() == o.yyyy && d.getMonth() + 1 == o.MM && d.getDate() == o.dd && d.getHours() == o.hh && d.getMinutes() == o.mm && d.getSeconds() == o.ss && d.getMilliseconds() == o.ms;
			return [reVal ? d : reVal, format];
			function s(s1, s2, s3, s4, s5){
				s4 = s4 || 60, s5 = s5 || 2;
				var reVal = s3;
				if(s1 != undefined && s1 != '' || !isNaN(s1)) reVal = s1 * 1;
				if(s2 != undefined && s2 != '' && !isNaN(s2)) reVal = s2 * 1;
				return (reVal == s1 && s1.length != s5 || reVal > s4) ? -10000 : reVal;
			}
		},
		lessThen: function(s){
			var a = s[0], b = s[1];
			return Validate.rule.isEmpty(a) && Validate.rule.isEmpty(b) || a < b;
		},
		greatThen: function(s){
			var a = s[0], b = s[1];
			return Validate.rule.isEmpty(a) && Validate.rule.isEmpty(b) || a > b;
		},
		equal: function(s){
			var a = s[0], b = s[1];
			return Validate.rule.isEmpty(a) && Validate.rule.isEmpty(b) || a == b;
		},
		min: function(s, limit){
			return [Validate.rule.isEmpty(s) || s >= limit, limit];
		},
		max: function(s, limit){
			return [Validate.rule.isEmpty(s) || s <= limit, limit];
		},
		minLen: function(s, limit){
			var l = s.length;
			return [l >= limit, limit, l];
		},
		maxLen: function(s, limit){
			var l = s.length;
			return [Validate.rule.isEmpty(s) || l <= limit, limit, l];
		},
		//special
		isAccount: function(){
			
		}
	},
	messages: {
		required: '必填。',
		isAlpha: '请输入字母。',
		isChinese: '请输入中文。',
		isInteger: '请输入整数',
		isFloat: '请输入浮点数。',
		isPhone: '请输入有效的电话号码。',
		isMobile: '请输入有效的手机号码。',
		isEmail: '请输入有效的电子邮件地址。',
		isURL: '请输入有效的网址。',
		isDatetime: new Template('请输入正确格式的日期。#{arg0}'),
		lessThen: 'A必须小于B。',
		greatThen: 'A必须大于B。',
		equal: 'A必须等于B。',
		min: new Template('最小为#{arg0}。'),
		max: new Template('最大为#{arg0}。'),
		minLen: new Template('最小长度为#{arg0}，当前为#{arg1}。'),
		maxLen: new Template('最大长度为#{arg0}，当前为#{arg1}。'),
		common: '验证失败'
	},
	
	utils: {
		initRules: function(rules){
			rules = Object.isString(rules) ? [rules] : rules;
			return rules.collect(function(rule){
				return Object.isString(rule) ? [rule] : rule;
			});		
		},
		getValidEvent: function(element){
			var type = Validate.utils.getElementType(element), evt = 'blur';
			if((type == 'checkbox' || type == 'radio') && Prototype.Browser.WebKit){
				evt = 'click';
			}
			return evt;
		},
		getElementType: function(el){
			switch(el.tagName.toLowerCase()){
				case 'input':
					return el.type;
				case 'select':
				case 'textarea':
					return el.tagName.toLowerCase();
			}
		},
		array2object: function(array){
			var obj = {}
			array.each(function(p, i){
				obj['arg' + i] = p;					
			})
			return obj;
		}
	}
};

Validate.rule.required.type = Validate.rule.isEmpty.type = Validate.rule.isAlpha.type = Validate.rule.isChinese.type = Validate.rule.isInteger.type = Validate.rule.isFloat.type = Validate.rule.isAlphaNum.type = Validate.rule.isMobile.type = Validate.rule.isPhone.type = Validate.rule.isEmail.type = Validate.rule.isURL.type = Validate.rule.isDatetime.type = 'single';


Validate.Form = Class.create({
	initialize: function(form, valids, options){
		this.element = $(form);
		this.valids = valids;
		this.options = Object.extend({
			stopAtFirst: true,
			onError: Prototype.emptyFunction,
			onOK: Prototype.emptyFunction
		}, options || {})
		this.element.observe('submit', this.validate.bindAsEventListener(this));
	},
	validate: function(e){
		var results = [];
		for(var i = 0, j = this.valids.length; i < j; ++ i){
			var result = this.valids[i].validate();
			if(result !== true)
				if(this.options.stopAtFirst){
					e.stop();
					result.scrollTo();
					return;
				}else
					results.push(result);
		}
		if(results.length){
			e.stop();
			results[0].scrollTo();
			return;
		}
		this.element.disable();
	}
});

Validate.Group = Class.create({
	initialize: function(elements, rules, options, messageOptions){
		this.elements = $.apply(null, elements);
		this.type = Validate.utils.getElementType(this.elements[0]);//assume elements have same type
		this.rules = Validate.utils.initRules(rules);
		this.options = Object.extend({
			onError: Prototype.emptyFunction,
			onOK: Prototype.emptyFunction,
			stopAtFirst: true,
			validOnBlur: true
		}, options || {})	
		messageOptions = messageOptions || {};
		Object.extend(messageOptions, {
			element: this.elements[0].up('div') || this.elements[this.elements.length - 1],
			rules: this.rules
		})
		this.message = new Validate.Message(messageOptions);
		
		this.eventCache = null; //for capture event correctlly

		this.elements[0].observe('valid:error', this.onError.bindAsEventListener(this));
		this.elements[0].observe('valid:ok', this.onOK.bindAsEventListener(this));
		
		if(this.options.validOnBlur){
			this.elements.each(function(el){
				//if((this.type == 'checkbox' || this.type == 'radio') && !Prototype.Browser.WebKit || this.type == 'text' || this.type == 'password'){
					el.observe('focus', function(){
						if(this.checkTimer)
							window.clearTimeout(this.checkTimer)
					}.bind(this));
					el.observe('blur', function(e){
						this.checkTimer = window.setTimeout(this.validate.bind(this), 100)
					}.bindAsEventListener(this));
				//}else
				//	el.observe(Validate.utils.getValidEvent(el), this.validate.bind(this));
			}, this)
		}
	},
	validate: function(){
		var s, summary = [];
		if(this.type == 'checkbox' || this.type == 'radio')
			s = this.elements.collect(function(el){if(el.checked)return el;}).compact();
		else	
			s = this.elements.collect(function(el){return el.getValue()});
		for(var i = 0, j = this.rules.length; i < j; ++ i){
			var args = this.rules[i].clone(),
				rule = args.shift(), result, passed;	
			if(Validate.rule[rule].type != 'single'){
				args.unshift(s);
				result = Validate.rule[rule].apply(null, args);
				if(!Object.isArray(result))result = [result];
				passed = result[0];
				if(!passed && this.options.stopAtFirst){
					result.shift();
					result.unshift(rule);
					this.elements[0].fire('valid:error', {error: [result]});
					return this.elements[0];
				}else{
					result.unshift(rule);
					summary.push(result);
				}
			}else{
				for(var m = 0, n = s.length; m < n; m ++){
					var _args = args.clone();
					_args.unshift(s[m]);
					result = Validate.rule[rule].apply(null, _args);
					if(!Object.isArray(result))result = [result];
					passed = result[0];
					if(!passed && this.options.stopAtFirst){
						result.shift();
						result.unshift(rule);
						this.elements[0].fire('valid:error', {error: [result]});
						return this.elements[0];
					}else{
						result.unshift(rule);
						summary.push(result);
					}					
				}
			}
		}
		
		var errors = summary.findAll(function(ruleResult){return !ruleResult[1];})
		if(errors.length){
			this.elements[0].fire('valid:error', {error: errors});
			return this.elements[0];
		}else{
			this.elements[0].fire('valid:ok');
			return true;
		}
	},
	onError: function(e){
		this.message.show(false, e.memo.error);
		this.options.onError(e.memo.error.collect(function(e){return e[0]}));//rules failed
	},
	onOK: function(){
		this.message.show(true);
		this.options.onOK();
	}
})

Validate.Element = Class.create({
	initialize: function(element, rules, options, messageOptions){
		this.element = $(element);
		this.type = Validate.utils.getElementType(this.element);
		this.rules = Validate.utils.initRules(rules);
		this.options = Object.extend({
			onError: Prototype.emptyFunction,
			onOK: Prototype.emptyFunction,
			stopAtFirst: true,
			validOnBlur: true
		}, options || {})
		
		messageOptions = messageOptions || {};
		Object.extend(messageOptions, {
			element: this.element,
			rules: this.rules
		})
		this.message = new Validate.Message(messageOptions);

		this.element.observe('valid:error', this.onError.bindAsEventListener(this));
		this.element.observe('valid:ok', this.onOK.bindAsEventListener(this));
		
		
		if(this.options.validOnBlur){
			this.element.observe(Validate.utils.getValidEvent(this.element), this.validate.bind(this));
		}
	},
	validate: function(){
		var s = $F(this.element), summary = [];
		for(var i = 0, j = this.rules.length; i < j; ++ i){
			var args = this.rules[i].clone(),
				rule = args.shift(), result, passed;
			args.unshift(s);
			
			//special rule process for special element
			if(this.type == 'select' && rule == 'required'){
				if(args.length == 1)args[1] = -1;
			}
			//end
			
			
			result = Validate.rule[rule].apply(null, args);
			
			if(!Object.isArray(result))result = [result];
			
			passed = result[0];
			
			if(!passed && this.options.stopAtFirst){
				result.shift();
				result.unshift(rule);
				this.element.fire('valid:error', {error: [result]});
				return this.element;
			}else{
				result.unshift(rule);
				summary.push(result);
			}
		}
		
		var errors = summary.findAll(function(ruleResult){return !ruleResult[1];})
		if(errors.length){
			this.element.fire('valid:error', {error: errors});
			return this.element;
		}else{
			this.element.fire('valid:ok');
			return true;
		}
	},
	onError: function(e){
		var errorRules = e.memo.error.collect(function(e){return e[0]})
		this.message.show(false, e.memo.error);
		this.options.onError(errorRules);
	},
	onOK: function(){
		this.message.show(true);
		this.options.onOK();
	},
	addGroupRule: function(rules){
		this.gRules = rules;
	}
	
});

Validate.Message = Class.create({
	initialize: function(options){
		this.options = Object.extend({
			element: null,
			rules: [],
			useTitle: false,
			effect: true, // true | false | Validate.CONST.INHERIT
			position: Validate.Message.position.AUTO,// id || Validate.Message.position.AUTO
			view: Validate.Message.view.BLOCK, // 
			messages: {}
		}, options || {})
		
		
		if(this.options.view !== Validate.Message.view.ALERT)
			if(this.options.position === Validate.Message.position.AUTO){
				this.position = new Element('div', {className: 'validate-messages'});
				this.options.element.insert({after: this.position});
			}else
				this.position = $(this.options.position);
	
		this.messages = {};
		this.messages.hint = this.options.messages.hint || (this.options.useTitle && this.options.element.title) || Validate.Message.message.NULL;
		this.messages.ok = this.options.messages.ok || Validate.Message.message.NULL;
		//for(var i = 0, j = this.options.rules.length; i < j; ++ i){
//			this.messages[this.options.rules[i][0] || this.options.rules[i]] = Object.extend({
//				error: Validate.Message.message.DEFAULT
//			}, this.options.messages[this.options.rules[i]] || {})
		//}
		
		this.options.rules.each(function(rule){
			var rule = rule[0] || rule;
			this.messages[rule] = this.options.messages[rule] || Validate.Message.message.DEFAULT;
		}, this)
		
		
		if(this.messages.hint)
			this.blockView('hint', [this.messages.hint]);
		
	},
	setMessage: function(message, rule, type){
		if(!(type in Validate.Message.type))
			throw 'invalid type of message @ Validate.Message::setMessage';
		if(!this.messages[rule])
			this.messages[rule] = {};
		this.messages[rule][type] = message;
	},
	setupMessage: function(result, errors){
		var messages;
		if(result){
			messages = [this.messages.ok];
		}else{
			messages = errors.collect(function(error){
				var rule = error.shift(),
					msg = (this.messages[rule] && this.messages[rule] === Validate.Message.message.DEFAULT) ? (Validate.messages[rule] || Validate.message.common) : this.messages[rule];
				return (msg instanceof Template) ? msg.evaluate(Validate.utils.array2object(error)) : msg;
			}, this)
		}
		messages = messages.without('');
		return messages.length ? messages : null;
	},
	show: function(result, errors){
		var message = this.setupMessage(result, errors);
		this[this.options.view + 'View'](result, message);
	},
	hide: function(){
		this.element.hide();
	},
	alertView: function(result, messages){
		if(messages)
			window.alert(messages.join('\n'));
	},
	blockView: function(result, messages){
		var className;
		if(result === true)
			className = 'v-ok';
		if(result === false)
			className = 'v-error';
		if(result == 'hint')
			className = 'v-hint';
		this.position.update();
		if(messages)
			messages.each(function(message){
				var el = new Element('div', {className: 'v-info ' + className, style: 'display: none;'}).update(message);
				this.position.insert(el);
				this.options.effect ? Effect.Appear(el) : el.show();
			}, this)
		
	}
})

Object.extend(Validate.Message, {
	type: {
		HINT: 'hint',
		ERROR: 'error',
		OK: 'ok'
	},
	view: {
		ALERT: 'alert',
		BLOCK: 'block'
	},
	message: {
		DEFAULT: 'Default',
		NULL: ''
	},
	position: {
		AUTO: 'auto',
		INHERIT: 'inherit'
	}
})